package com.blacksee.bluetoothpoker.network;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import com.blacksee.bluetoothpoker.logic.Deck;
import com.blacksee.bluetoothpoker.logic.net.mypapit.java.StringTokenizer;
import com.blacksee.bluetoothpoker.network.messages.AcceptanceResponseMessage;
import com.blacksee.bluetoothpoker.network.messages.BetMessage;
import com.blacksee.bluetoothpoker.network.messages.CardsMessage;
import com.blacksee.bluetoothpoker.network.messages.ExistingTablesMessage;
import com.blacksee.bluetoothpoker.network.messages.NetMessage;
import com.blacksee.bluetoothpoker.network.messages.PlayerRequestMessage;
import com.blacksee.bluetoothpoker.network.messages.QuitGameMessage;
import com.blacksee.bluetoothpoker.network.messages.StartGameMessage;



public class NetCommunicator implements NetworkInterface {

	private static NetworkInterface instance = null;

	private int myID;
	private String myName;

	private IMessageSender sender;
	private Vector queue;
	
	private Deck deckOfCards;

	private NetCommunicator() {
		queue = new Vector(5);
	}

	public synchronized static NetworkInterface getInstance(){
		if (instance == null) {
			instance = new NetCommunicator();
		}
		return instance;
	}

	public int getMyId() {
		return myID;
	}

	public String getMyName() {
		return myName;
	}

	public void setMyName(String name){
		myName = name;
	}

	public void betAction(int amount, boolean isAllIn) throws BlueNetException {
		BetMessage objMsg = new BetMessage(getMyId(), BetMessage.ACTION_BET, amount, isAllIn);
		// TODO: where to put isAllIn parameter
		String mssg = encodeMessage(objMsg);

		// send the message to yourself first
		queue.addElement(objMsg);
		if (sender != null) {
			// broadcast betting to all, except you
			try {
				sender.broadcastMessage(mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMsg);
				throw e;
			}
		}
	}

	public void callAction(boolean isAllIn) throws BlueNetException {
		BetMessage objMsg = new BetMessage(getMyId(), BetMessage.ACTION_CALL);
		// TODO: where to put isAllIn parameter
		String mssg = encodeMessage(objMsg);

		// send the message to yourself first
		queue.addElement(objMsg);
		if (sender != null) {
			try {
				// broadcast betting to all
				sender.broadcastMessage(mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMsg);
				throw e;
			}
		}
	}

	public void checkAction() throws BlueNetException {
		BetMessage objMsg = new BetMessage(getMyId(), BetMessage.ACTION_CHECK);
		String mssg = encodeMessage(objMsg);

		// send the message to yourself first
		queue.addElement(objMsg);
		if (sender != null) {
			try {
				// broadcast betting to all
				sender.broadcastMessage(mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMsg);
				throw e;
			}
		}
	}

	public void foldAction() throws BlueNetException {
		BetMessage objMsg = new BetMessage(getMyId(), BetMessage.ACTION_FOLD);
		String mssg = encodeMessage(objMsg);

		// send the message to yourself first
		queue.addElement(objMsg);
		if (sender != null) {
			try {
				// broadcast betting to all
				sender.broadcastMessage(mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMsg);
				throw e;
			}
		}
	}

	public void createGame(String name) {
		// created by the server, it creates the table 
		// should publish a table(server) service to be accessed by clients
		myID = -1;
		final BluePokerServerImpl server = new BluePokerServerImpl();
		server.setServiceName(name);
		
		Thread serverThread = new Thread(new Runnable(){

			public void run() {		
				server.startServer();	
			}

		});

		serverThread.start();

		sender = server;
	}

	public void joinGame(TableBean table) throws BlueNetException {

		if (sender != null && sender instanceof BluePokerClientImpl) {
			// send a message only to server
			final int tableID = table.getTableId(); 
			Thread joinGameThread = new Thread(new Runnable(){

				public void run() {
					try {
						PlayerRequestMessage request = new PlayerRequestMessage(myName);
						String mssg = encodeMessage(request);
						((BluePokerClientImpl)sender).createConnectionToServer(tableID);
						sender.sendMessage(0, mssg);
					} catch (BlueNetException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

			});
			joinGameThread.start();
		}
	}

	public void acceptPlayer(String name, int id) throws BlueNetException {
		AcceptanceResponseMessage objMsg = new AcceptanceResponseMessage(true,name,id);
		String mssg = encodeMessage(objMsg);

		//queue.addElement(request);

		if (sender != null) {
			try {
				// send a message only to client
				sender.sendMessage(id, mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMsg);
				e.setFailedDeviceID(id);
				e.setFailedDeviceName(name);
				throw e;
			}
		}
	}	

	public void startGame(Hashtable players) throws BlueNetException {
		// stops accepting players and starts the selected game, the one sent to joinGame
		// it sends the list of all players that joined the game
		players.put(new Integer(myID), myName);
		
		deckOfCards = new Deck(37);
		deckOfCards.shuffle();

		// send the message to yourself first
		Vector cards = new Vector();
		cards.addElement(deckOfCards.dealCard());
		cards.addElement(deckOfCards.dealCard());
		StartGameMessage objMsg = new StartGameMessage(players, cards);
		String msg = encodeMessage(objMsg);

		queue.addElement(objMsg);
		
		if (sender != null) {
			Enumeration e = players.keys();
			while (e.hasMoreElements()) {
				Integer clientID = (Integer)e.nextElement();
				if(clientID.intValue() == myID){
					continue;
				}
				try {
					// send a message to all that the game have begun with their cards
					cards = new Vector();
					cards.addElement(deckOfCards.dealCard());
					cards.addElement(deckOfCards.dealCard());
					objMsg = new StartGameMessage(players, cards);
					msg = encodeMessage(objMsg);
					
					System.out.println("Sending start game message to client: " + clientID);
					sender.sendMessage(clientID.intValue(), msg);
				} catch (BlueNetException ex) {
					ex.setFailedMessage(objMsg);
					ex.printStackTrace();
				}
			}
			
			((BluePokerServerImpl)sender).setInquiryReady(false);
		}
	}
	
	public void showCards(String cards, int value) {
		// TODO Auto-generated method stub

	}

	public void askForCards(int number) throws BlueNetException {
		CardsMessage objMssg = new CardsMessage(getMyId(), number);
		String mssg = encodeMessage(objMssg);
		
		if (sender != null) {
			// check if you are the server - deal the cards
			if (sender instanceof BluePokerServerImpl) {
				dealCards(objMssg);
			} else {
				try {
					// send a message only to server
					sender.sendMessage(0, mssg);
				} catch (BlueNetException e) {
					e.setFailedMessage(objMssg);
					throw e;
				}
			}
		}
	}

	public void quitGame() throws BlueNetException {
		QuitGameMessage objMssg = new QuitGameMessage(getMyId());
		String mssg = encodeMessage(objMssg);

		// send the message to yourself first
		queue.addElement(objMssg);
		if (sender != null) {
			try {
				// broadcast betting to all
				sender.broadcastMessage(mssg);
			} catch (BlueNetException e) {
				e.setFailedMessage(objMssg);
				throw e;
			}
		}
	}



	// receive low level network messages
	public void receiveMssage(String msg, int senderID) {
		try {
			// based on the msg type create specialized types of NetMessage
			NetMessage netMessage = decodeMessage(msg, senderID);
			
			if (netMessage instanceof AcceptanceResponseMessage) {
				AcceptanceResponseMessage acceptMsg = (AcceptanceResponseMessage) netMessage;
				if (acceptMsg.isAccepted()) {
					this.myID = acceptMsg.getAcceptedId();
					this.myName = acceptMsg.getAcceptedName();
				}
				
				System.out.println("Device " + myID + ":" + myName + " is accepted: " + acceptMsg.isAccepted());
			} else if (netMessage instanceof PlayerRequestMessage) {
				PlayerRequestMessage playerMssg = (PlayerRequestMessage) netMessage;
				playerMssg.setSenderID(senderID);
			}
			
			if (netMessage instanceof CardsMessage && 
					sender != null &&  
					sender instanceof BluePokerServerImpl) {
				// I'm a server
				CardsMessage objMssg = (CardsMessage)netMessage;
				dealCards(objMssg);
			} else {
				queue.addElement(netMessage);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Receive how many cards you should deal, generate them and 
	 * send them back to client.
	 * 
	 * @param objMssg
	 */
	private void dealCards(CardsMessage objMssg) {
		StringBuffer cards = new StringBuffer();
		for (int i = 0; i < objMssg.getNumCards(); i++) {
			cards.append(deckOfCards.dealCard().toString());
		}
		objMssg.setCards(cards.toString());
		
		int clientID = objMssg.getSenderID();
		CardsMessage objNewMssg = new CardsMessage(0, objMssg.getNumCards());
		objNewMssg.setCards(objMssg.getCards());
		String mssg = encodeMessage(objNewMssg);
		
		// forward the message to the client without putting it in queue
		if (sender != null) {
			// check if you are the client - send it to queue
			if (clientID == getMyId()) {
				queue.addElement(objNewMssg);
			} else {
				try {
					// send a message only to client
					sender.sendMessage(clientID, mssg);
				} catch (BlueNetException e) {
					e.setFailedMessage(objMssg);
					e.printStackTrace();
				}
			}
		}
	}

	public void receiveMssage(NetMessage objMssg) {
		System.out.println("Message added to the queue");
		queue.addElement(objMssg);
	}

	private String encodeMessage(NetMessage netMsg)	{
		return netMsg.GetMessageString();
	}

	private NetMessage decodeMessage(String msg, int senderID) {
		NetMessage netMessage = null;

		StringTokenizer tok = new StringTokenizer(msg,"*");
				
		String messageHead = tok.nextToken();

		System.out.println("HEADER : " + messageHead); //TODO remove
		
		if(messageHead.equals("START_GAME")) //0
		{
			netMessage = new StartGameMessage();
		}
		else if(messageHead.equals("ACTION_BET")) //1
		{
			netMessage = new BetMessage(senderID, 1);
		}
		else if(messageHead.equals("ACTION_CALL")) //2
		{
			netMessage = new BetMessage(senderID, 2 );
		}
		else if(messageHead.equals("ACTION_FOLD")) //3
		{
			netMessage = new BetMessage(senderID, 3 );
		}
		else if(messageHead.equals("ACTION_CHECK")) //4
		{
			netMessage = new BetMessage(senderID, 4 );
		}
		else if(messageHead.equals("QUIT_GAME")) //5
		{
			netMessage = new QuitGameMessage(senderID);
		}
		else if(messageHead.equals("RECEIVE_CARD")) //6
		{
			netMessage = new CardsMessage();
		}
		else if(messageHead.equals("EXISTING_TABLES")) //7
		{
			netMessage = new ExistingTablesMessage();
		}
		else if(messageHead.equals("PLAYER_REQUEST")) //8
		{
			netMessage = new PlayerRequestMessage();
		}
		else if(messageHead.equals("ACCEPTANCE_RESPONSE")) //9
		{ 
			netMessage = new AcceptanceResponseMessage(true); // player accepted in game
		}

		
		// parse the parameters of the message
		netMessage.parseParameters(msg);
		
		return netMessage;
	}

	public void findTables() {
		final BluePokerClientImpl client = new BluePokerClientImpl();
		this.sender = client;

		Thread clientThread = new Thread(new Runnable() {
			public void run() {
				client.startClient();
			}
		});
		clientThread.start();
	}

	public NetMessage popMssage() {
		// TODO Pop first message in the message queue
		NetMessage msg = null;
		if (queue.size() > 0) {
			msg = (NetMessage) queue.firstElement();
			queue.removeElementAt(0);
		}
		return msg;
	}

	public void setMessageSender(IMessageSender sender) {
		this.sender = sender;
	}

}


